package br.edu.ufcg.splab.simulator.clique.graph;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * A class that represents a undirected graph, and contains 
 * collections of vertex and edges, to establish 
 * its structure. 
 * A graph is composed by a list of vertex and a list of edges.
 * Each vertex known your neighbors.
 * 
 **/
public class UndirectedGraph{
	private List<Vertex> vertices;
	private List<Edge> edges;
	
	private Map<Vertex, Set<Vertex>> mapNeighbors;
	
	public UndirectedGraph(){
		vertices = new ArrayList<Vertex>();
		edges = new ArrayList<Edge>();
		mapNeighbors = new HashMap<Vertex, Set<Vertex>>();
	}
	
	/**
	 * Adds a vertex in the graph.
	 * @param vertex The vertex to be added.
	 */
	public void addVertex(Vertex vertex){
		vertices.add(vertex);
	}
	
	/**
	 * Adds a edge in the graph from two neighbors vertices.
	 * 
	 * @return 
	 */
	public boolean addEdge(Vertex vertex1, Vertex vertex2){
		if(mapNeighbors.containsKey(vertex1)){
			if(mapNeighbors.get(vertex1).contains(vertex2)){
				return false;
			}
		}else{
			if(mapNeighbors.containsKey(vertex2)){
				if(mapNeighbors.get(vertex2).contains(vertex1)){
					return false;
				}
			}
		}
		edges.add(new Edge(edges.size() + 1));
		addNeighbors(vertex1,vertex2);	
		return true;
	}
	
	/**
	 * Removes a vertex of the graph.
	 * 
	 * @param The vertex to be removed.
	 */
	public void removeVertex(Vertex vertex){
		vertices.remove(vertex);
		if(mapNeighbors.containsKey(vertex)){
			for(Vertex v:mapNeighbors.get(vertex)){
				mapNeighbors.get(v).remove(vertex);
			}
			mapNeighbors.remove(vertex);
		}
	}
		
	/**
	 * Returns the vertices of the graph.
	 * @return The vertices of the graph.
	 */
	public List<Vertex> getVertices(){
		return vertices;
	}
	
	/**
	 * Returns the number of vertex of the graph.
	 * @return The number of vertex of the graph.
	 */
	public int getVertexCount(){
		return vertices.size();
	}
	
	/**
	 * Returns the neighbors of a vertex in the graph.
	 * @param vertex The vertex to obtain their neighbors.
	 * @return The neighbors of the vertex in the graph.
	 */
	public Set<Vertex> getNeighbors(Vertex vertex){
		return mapNeighbors.get(vertex);
	}
	
	/**
	 * Returns all vertices without neighbors.
	 * @return All vertices without neighbors.
	 */
	public List<Vertex> getVertexNoNeighbors(){
		List<Vertex> noNeighbors = new ArrayList<Vertex>();
		for(Vertex v: vertices){
			if(!mapNeighbors.containsKey(v)){
				noNeighbors.add(v);
			}
		}
		return noNeighbors;
	}
		
	public String toString(){
		String str = "";
		Set<Entry<Vertex,Set<Vertex>>> set = mapNeighbors.entrySet();
		Iterator<Entry<Vertex,Set<Vertex>>> it = set.iterator();
		while(it.hasNext()){
			Entry<Vertex,Set<Vertex>> entry = it.next();
			str += entry.getKey().toString() + ": ";
			for(Vertex node:entry.getValue()){
				str += node.toString() + ", ";
			}
			str += "\n";
		}
		return str;
	}
	
	/*
	 * Adds vertices neighbors.
	 */
	private void addNeighbors(Vertex vertex1, Vertex vertex2){
		if(mapNeighbors.containsKey(vertex1)){
			mapNeighbors.get(vertex1).add(vertex2);
		}else{
			Set<Vertex> neighbors = new HashSet<Vertex>();
			neighbors.add(vertex2);
			mapNeighbors.put(vertex1, neighbors);
		}
		if(mapNeighbors.containsKey(vertex2)){
			mapNeighbors.get(vertex2).add(vertex1);
		}else{
			Set<Vertex> neighbors = new HashSet<Vertex>();
			neighbors.add(vertex1);
			mapNeighbors.put(vertex2, neighbors);
		}
	}

}
